/***************************************************************************
 *   Copyright (C) 2005 by David Saxton                                    *
 *   david@bluehaze.org                                                    *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 ***************************************************************************/

#include "flipflop.h"
#include "ecnode.h"
#include "icndocument.h"
#include "libraryitem.h"
#include "logic.h"
#include "node.h"
#include "simulator.h"

#include <KLocalizedString>
#include <QPainter>

// BEGIN class ClockedFlipFlop
ClockedFlipFlop::ClockedFlipFlop(ICNDocument *icnDocument, bool newItem, const char *id)
    : Component(icnDocument, newItem, id)
{
    createProperty("trig", Variant::Type::Select);
    property("trig")->setCaption(i18n("Trigger Edge"));
    QStringMap allowed;
    allowed["Rising"] = i18n("Rising");
    allowed["Falling"] = i18n("Falling");
    property("trig")->setAllowed(allowed);
    property("trig")->setValue("Rising");
    m_edgeTrigger = Rising;
}

void ClockedFlipFlop::dataChanged()
{
    EdgeTrigger t = (dataString("trig") == "Rising") ? Rising : Falling;
    if (t == m_edgeTrigger)
        return;

    m_edgeTrigger = t;
    initSymbolFromTrigger();
}
// END class ClockedFlipFlop

// BEGIN class ECDFlipFlop
Item *ECDFlipFlop::construct(ItemDocument *itemDocument, bool newItem, const char *id)
{
    return new ECDFlipFlop(dynamic_cast<ICNDocument *>(itemDocument), newItem, id);
}

LibraryItem *ECDFlipFlop::libraryItem()
{
    return new LibraryItem(QStringList(QString("ec/d_flipflop")), i18n("D Flip-Flop"), i18n("Integrated Circuits"), "ic3.png", LibraryItem::lit_component, ECDFlipFlop::construct);
}

ECDFlipFlop::ECDFlipFlop(ICNDocument *icnDocument, bool newItem, const char *id)
    : ClockedFlipFlop(icnDocument, newItem, id ? id : "d_flipflop")
{
    m_name = i18n("D-Type Flip-Flop");

    setSize(-32, -24, 64, 48);
    init2PinLeft(-8, 8);
    init2PinRight(-8, 8);
    initSymbolFromTrigger();

    m_prevD = false;
    m_pSimulator = Simulator::self();

    m_bPrevClock = false;
    m_pD = createLogicIn(m_pNNode[0]);
    m_pClock = createLogicIn(m_pNNode[1]);
    m_pQ = createLogicOut(m_pPNode[0], false);
    m_pQBar = createLogicOut(m_pPNode[1], false);

    setp = createLogicIn(createPin(0, -32, 90, "set"));
    rstp = createLogicIn(createPin(0, 32, 270, "rst"));

    // (The display text for D, >, Set, Rst is set in initSymbolFromTrigger
    addDisplayText("Q", QRect(12, -16, 20, 16), "Q");
    addDisplayText("Q'", QRect(12, 0, 20, 16), "Q'");

    m_pD->setCallback(this, static_cast<CallbackPtr>(&ECDFlipFlop::inputChanged));
    m_pClock->setCallback(this, static_cast<CallbackPtr>(&ECDFlipFlop::clockChanged));
    setp->setCallback(this, static_cast<CallbackPtr>(&ECDFlipFlop::asyncChanged));
    rstp->setCallback(this, static_cast<CallbackPtr>(&ECDFlipFlop::asyncChanged));

    inStateChanged(false);
}

ECDFlipFlop::~ECDFlipFlop()
{
    m_pD->setCallback(nullptr, static_cast<CallbackPtr>(nullptr));
    m_pClock->setCallback(nullptr, static_cast<CallbackPtr>(nullptr));
    setp->setCallback(nullptr, static_cast<CallbackPtr>(nullptr));
    rstp->setCallback(nullptr, static_cast<CallbackPtr>(nullptr));
}

void ECDFlipFlop::initSymbolFromTrigger()
{
    int offset = (m_edgeTrigger == Rising) ? 0 : 6;

    int w = 64 - offset;
    setSize(offset - 32, -24, w, 48, true);
    m_pNNode[0]->setLength(8 + offset);
    addDisplayText("D", QRect(offset - 28, -16, 20, 16), "D", true, Qt::AlignLeft);
    addDisplayText(">", QRect(offset - 28, 0, 20, 16), ">", true, Qt::AlignLeft);
    addDisplayText("Set", QRect(offset - 28, -20, w - 8, 16), "Set", true, Qt::AlignHCenter);
    addDisplayText("Rst", QRect(offset - 28, 4, w - 8, 16), "Rst", true, Qt::AlignHCenter);

    updateAttachedPositioning();
}

void ECDFlipFlop::drawShape(QPainter &p)
{
    Component::drawShape(p);

    if (m_edgeTrigger == Falling) {
        initPainter(p);
        p.drawEllipse(int(x() - 32), int(y() + 5), 6, 6);
        deinitPainter(p);
    }
}

void ECDFlipFlop::asyncChanged(bool)
{
    bool set = setp->isHigh();
    bool rst = rstp->isHigh();
    if (set || rst) {
        m_pQ->setHigh(set);
        m_pQBar->setHigh(rst);
    }
}

void ECDFlipFlop::inputChanged(bool newState)
{
    if (newState == m_prevD) {
        // Only record the time that the input state changes
        return;
    }

    m_prevD = newState;
    m_prevDChangeSimTime = m_pSimulator->time();
}

void ECDFlipFlop::clockChanged(bool newState)
{
    bool set = setp->isHigh();
    bool rst = rstp->isHigh();

    bool fallingEdge = m_bPrevClock && !newState;
    bool edge = (m_edgeTrigger == Falling) ? fallingEdge : !fallingEdge;

    m_bPrevClock = newState;

    if (set || rst)
        return;

    if (edge) {
        // The D Flip-Flop takes the input before the edge fall/rise - not after
        // the edge. So see if the input state last changed before now or at
        // now to work out if we should take the current value of m_prevD, or
        // its inverse.

        unsigned long long simTime = m_pSimulator->time();
        bool d = (simTime == m_prevDChangeSimTime) ? !m_prevD : m_prevD;

        m_pQ->setHigh(d);
        m_pQBar->setHigh(!d);
    }
}

void ECDFlipFlop::inStateChanged(bool)
{
    // Only called when the flipflop is created.
    m_pQ->setHigh(false);
    m_pQBar->setHigh(true);
}
// END class ECDFlipFlop

// BEGIN class ECJKFlipFlop
Item *ECJKFlipFlop::construct(ItemDocument *itemDocument, bool newItem, const char *id)
{
    return new ECJKFlipFlop(dynamic_cast<ICNDocument *>(itemDocument), newItem, id);
}

LibraryItem *ECJKFlipFlop::libraryItem()
{
    return new LibraryItem(QStringList(QString("ec/jk_flipflop")), i18n("JK Flip-Flop"), i18n("Integrated Circuits"), "ic3.png", LibraryItem::lit_component, ECJKFlipFlop::construct);
}

ECJKFlipFlop::ECJKFlipFlop(ICNDocument *icnDocument, bool newItem, const char *id)
    : ClockedFlipFlop(icnDocument, newItem, id ? id : "jk_flipflop")
{
    m_name = i18n("JK-Type Flip-Flop");

    setSize(-32, -32, 64, 64);
    init3PinLeft(-16, 0, 16);
    init2PinRight(-16, 16);
    initSymbolFromTrigger();

    m_bPrevClock = false;

    createProperty("trig", Variant::Type::Select);
    property("trig")->setCaption(i18n("Trigger Edge"));
    QStringMap allowed;
    allowed["Rising"] = i18n("Rising");
    allowed["Falling"] = i18n("Falling");
    property("trig")->setAllowed(allowed);
    property("trig")->setValue("Rising");
    m_edgeTrigger = Rising;
    initSymbolFromTrigger();

    m_pJ = createLogicIn(m_pNNode[0]);
    m_pClock = createLogicIn(m_pNNode[1]);
    m_pK = createLogicIn(m_pNNode[2]);

    m_pQ = createLogicOut(m_pPNode[0], false);
    m_pQBar = createLogicOut(m_pPNode[1], false);

    setp = createLogicIn(createPin(0, -40, 90, "set"));
    rstp = createLogicIn(createPin(0, 40, 270, "rst"));

    addDisplayText("Q", QRect(12, -24, 20, 16), "Q");
    addDisplayText("Q'", QRect(12, 8, 20, 16), "Q'");

    m_pClock->setCallback(this, static_cast<CallbackPtr>(&ECJKFlipFlop::clockChanged));
    setp->setCallback(this, static_cast<CallbackPtr>(&ECJKFlipFlop::asyncChanged));
    rstp->setCallback(this, static_cast<CallbackPtr>(&ECJKFlipFlop::asyncChanged));

    inStateChanged(false);
}

ECJKFlipFlop::~ECJKFlipFlop()
{
    m_pClock->setCallback(nullptr, static_cast<CallbackPtr>(nullptr));
    setp->setCallback(nullptr, static_cast<CallbackPtr>(nullptr));
    rstp->setCallback(nullptr, static_cast<CallbackPtr>(nullptr));
}

void ECJKFlipFlop::initSymbolFromTrigger()
{
    int offset = (m_edgeTrigger == Rising) ? 0 : 6;

    int w = 64 - offset;
    setSize(offset - 32, -32, w, 64, true);
    m_pNNode[0]->setLength(8 + offset);
    m_pNNode[2]->setLength(8 + offset);
    addDisplayText("J", QRect(offset - 28, -24, 20, 16), "J", true, Qt::AlignLeft);
    addDisplayText(">", QRect(offset - 28, -8, 20, 16), ">", true, Qt::AlignLeft);
    addDisplayText("K", QRect(offset - 28, 8, 20, 16), "K", true, Qt::AlignLeft);
    addDisplayText("Set", QRect(offset - 28, -28, w - 8, 16), "Set", true, Qt::AlignHCenter);
    addDisplayText("Rst", QRect(offset - 28, 12, w - 8, 16), "Rst", true, Qt::AlignHCenter);

    updateAttachedPositioning();
}

void ECJKFlipFlop::drawShape(QPainter &p)
{
    Component::drawShape(p);

    if (m_edgeTrigger == Falling) {
        initPainter(p);
        p.drawEllipse(int(x() - 32), int(y() - 3), 6, 6);
        deinitPainter(p);
    }
}

void ECJKFlipFlop::clockChanged(bool newvalue)
{
    bool fallingEdge = (m_bPrevClock && !newvalue);
    bool edge = (m_edgeTrigger == Falling) ? fallingEdge : !fallingEdge;
    m_bPrevClock = newvalue;

    bool j = m_pJ->isHigh();
    bool k = m_pK->isHigh();
    bool set = setp->isHigh();
    bool rst = rstp->isHigh();

    if (set || rst)
        return;

    // a JK flip-flop change state when clock do 1->0
    if (edge && (j || k)) {
        if (j && k) {
            m_pQ->setHigh(!prev_state);
            m_pQBar->setHigh(prev_state);
            prev_state = !prev_state;
        } else {
            // (J=1 && K=0) || (J=0 && K=1)
            m_pQ->setHigh(j);
            m_pQBar->setHigh(k);
            prev_state = j;
        }
    }
}

void ECJKFlipFlop::asyncChanged(bool)
{
    bool set = setp->isHigh();
    bool rst = rstp->isHigh();

    if (set || rst) {
        m_pQ->setHigh(set);
        m_pQBar->setHigh(rst);
        prev_state = set;
    }
}

void ECJKFlipFlop::inStateChanged(bool)
{
    m_pQBar->setHigh(true);
    m_pQ->setHigh(false);
    prev_state = false;
}
// END class ECJKFlipFlop

// BEGIN class ECSRFlipFlop
Item *ECSRFlipFlop::construct(ItemDocument *itemDocument, bool newItem, const char *id)
{
    return new ECSRFlipFlop(dynamic_cast<ICNDocument *>(itemDocument), newItem, id);
}

LibraryItem *ECSRFlipFlop::libraryItem()
{
    return new LibraryItem(QStringList(QString("ec/sr_flipflop")), i18n("SR Flip-Flop"), i18n("Integrated Circuits"), "ic3.png", LibraryItem::lit_component, ECSRFlipFlop::construct);
}

ECSRFlipFlop::ECSRFlipFlop(ICNDocument *icnDocument, bool newItem, const char *id)
    : Component(icnDocument, newItem, id ? id : "sr_flipflop")
{
    m_name = i18n("SR Flip-Flop");

    setSize(-24, -24, 48, 48);

    init2PinLeft(-8, 8);
    init2PinRight(-8, 8);

    m_pS = createLogicIn(m_pNNode[0]);
    m_pR = createLogicIn(m_pNNode[1]);
    m_pQ = createLogicOut(m_pPNode[0], true);
    m_pQBar = createLogicOut(m_pPNode[1], false);

    old_q1 = true;
    old_q2 = false;
    m_pQ->setHigh(old_q1);
    m_pQBar->setHigh(old_q2);

    addDisplayText("S", QRect(-24, -16, 20, 16), "S");
    addDisplayText("R", QRect(-24, 0, 20, 16), "R");
    addDisplayText("Q", QRect(4, -16, 20, 16), "Q");
    addDisplayText("Q'", QRect(4, 0, 20, 16), "Q'");

    m_pS->setCallback(this, static_cast<CallbackPtr>(&ECSRFlipFlop::inStateChanged));
    m_pR->setCallback(this, static_cast<CallbackPtr>(&ECSRFlipFlop::inStateChanged));
    m_pQ->setCallback(this, static_cast<CallbackPtr>(&ECSRFlipFlop::inStateChanged));
    m_pQBar->setCallback(this, static_cast<CallbackPtr>(&ECSRFlipFlop::inStateChanged));
}

ECSRFlipFlop::~ECSRFlipFlop()
{
    m_pS->setCallback(nullptr, static_cast<CallbackPtr>(nullptr));
    m_pR->setCallback(nullptr, static_cast<CallbackPtr>(nullptr));
    m_pQ->setCallback(nullptr, static_cast<CallbackPtr>(nullptr));
    m_pQBar->setCallback(nullptr, static_cast<CallbackPtr>(nullptr));
}

void ECSRFlipFlop::inStateChanged(bool)
{
    // Q = v_q1, Q-bar = v_q2
    bool new_q1 = false;
    bool new_q2 = false;

    bool s = m_pS->isHigh();
    bool r = m_pR->isHigh();
    bool q1 = m_pQ->isHigh();
    bool q2 = m_pQBar->isHigh();

    // Easy ones to do :-)
    if (!q1)
        new_q2 = true;
    if (!q2)
        new_q1 = true;

    if (q1 && q2) {
        if (s && !r) {
            new_q1 = true;
            new_q2 = false;
        } else if (!s && r) {
            new_q1 = false;
            new_q2 = true;
        } else if (s && r) {
            new_q1 = old_q1;
            new_q2 = old_q2;
        } else if (!s && !r) {
            new_q1 = false;
            new_q2 = false;
        }
    } else if (q1 && !q2) {
        // Note: We only need to set the value of v_q2
        if (r && !s)
            new_q2 = true;
        else
            new_q2 = false;
    } else if (!q1 && q2) {
        // Note: We only need to set the value of v_q1
        if (s && !r)
            new_q1 = true;
        else
            new_q1 = false;
    }

    old_q1 = new_q1;
    old_q2 = new_q2;

    m_pQ->setHigh(new_q1);
    m_pQBar->setHigh(new_q2);
}
// END class ECSRFlipFlop
